﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace WentStateMachine
{
    [CreateAssetMenu(fileName = "StateMechineController", menuName = "WentStateMechine/StateMechineController")]
    public class StateMachineController : ScriptableObject, IStateMachineController
    {
        private Dictionary<string, object> GlobalParams = new Dictionary<string, object>();

        public string AnimatorControllerGUID;
        public bool ParamHasChanged
        {
            get { return paramHasChanged; }
        }

        private bool isStarted = false;

        [SerializeField] private Layer[] _layers;
        public Layer[] layers {
            get
            {
                return _layers;
            }
            set
            {
                _layers = value;
            }
        }

        private bool paramHasChanged = true;
        private Dictionary<int, Parameter> paramNameMap;
        private Parameter[] triggers;
        [SerializeField] private Parameter[] _parameters;
        public Parameter[] parameters {
            get
            {
                return _parameters;
            }
            set
            {
                _parameters = value;
            }
        }

        public void Init() {
            Layer layer;
            for (int i = 0; i < layers.Length; i++)
            {
                layer = layers[i];
                layer.Init(this, i);
            }

            if (parameters != null)
            {
                paramNameMap = new Dictionary<int, Parameter>();
                List<Parameter> triggerL = new List<Parameter>();
                for (var i = 0; i < parameters.Length; i++)
                {
                    paramNameMap.Add(parameters[i].nameHash, parameters[i]);
                    if (parameters[i].type == ParameterType.Trigger)
                    {
                        triggerL.Add(parameters[i]);
                    }
                }
                triggers = triggerL.ToArray();
            }
        }

        public void OnUpdate()
        {
            if (!isStarted)
                return;

            if (paramHasChanged)
            {
                foreach (var layer in layers)
                {
                    layer.stateMachine.Verify(paramNameMap);
                }

                resetTriggers();
                paramHasChanged = false;
            }

            foreach (var layer in layers)
            {
                layer.stateMachine.OnUpdate();
            }
        }

        private void resetTriggers()
        {
            foreach(var param in triggers)
            {
                param.defaultBool = false;
            }
        }

        public void Play(string stateName)
        {
            Play(Helper.ScriptingStringToCRC32(stateName));
        }

        public void Play(string stateName, int layerIndex)
        {
            Play(Helper.ScriptingStringToCRC32(stateName), layerIndex);
        }

        public void Play(int stateNameHash)
        {
            Play(stateNameHash, 0);
        }

        public void Play(int stateNameHash, int layerIndex)
        {
            layers[layerIndex].stateMachine.Play(stateNameHash);
        }

        public void SetBool(string name, bool value)
        {
            SetBool(Helper.ScriptingStringToCRC32(name), value);
        }

        public void SetBool(int id, bool value)
        {
            var param = paramNameMap[id];
            if (param.type == ParameterType.Bool)
            {
                if(param.defaultBool != value)
                {
                    param.defaultBool = value;
                    paramHasChanged = true;
                }
            }
            else
            {
                throw new ArgumentException($"参数 {name} 的类型为 {param.type} 不可调用 SetBool");
            }
        }

        public void SetFloat(string name, float value)
        {
            SetFloat(Helper.ScriptingStringToCRC32(name), value);
        }

        public void SetFloat(int id, float value)
        {
            var param = paramNameMap[id];
            if (param.type == ParameterType.Float)
            {
                if(param.defaultFloat != value)
                {
                    param.defaultFloat = value;
                    paramHasChanged = true;
                }
            }
            else
            {
                throw new ArgumentException($"参数 {name} 的类型为 {param.type} 不可调用 SetFloat");
            }
        }

        public void SetInteger(string name, int value)
        {
            SetInteger(Helper.ScriptingStringToCRC32(name), value);
        }

        public void SetInteger(int id, int value)
        {
            var param = paramNameMap[id];
            if (param.type == ParameterType.Int)
            {
                if(param.defaultInt != value)
                {
                    param.defaultInt = value;
                    paramHasChanged = true;
                }
            }
            else
            {
                throw new ArgumentException($"参数 {name} 的类型为 {param.type} 不可调用 SetInt");
            }
        }

        public void SetTrigger(string name)
        {
            SetTrigger(Helper.ScriptingStringToCRC32(name));
        }

        public void SetTrigger(int id)
        {
            var param = paramNameMap[id];
            if (param.type == ParameterType.Trigger)
            {
                param.defaultBool = true;
                paramHasChanged = true;
            }
            else
            {
                throw new ArgumentException($"参数 {name} 的类型为 {param.type} 不可调用 SetTrigger");
            }
        }

        public void Start()
        {
            isStarted = true;
        }

        public void Stop()
        {
            isStarted = false;
        }

        public IStateInfo GetCurrentStateInfo(int layerIndex)
        {
            return layers[layerIndex].stateMachine.currentState;
        }

        public void SetGlobalParam(string name, object param)
        {
            if (GlobalParams.ContainsKey(name))
                GlobalParams[name] = param;
            else
                GlobalParams.Add(name, param);
        }

        public object GetGlobalParam(string name)
        {
            return GlobalParams[name];
        }
    }
}
